/*
 关于JS的兼容处理
   + ES6+(ES2015+) 不兼容IE浏览器、支持Edge、谷歌、火狐等浏览器
   + ES5 不兼容IE6~8
 我们所谓的兼容处理，其实就是把ES6+转换为ES5
   + ES6“语法”的转换：babel-loader、@babel/preset-env
     + https://babeljs.io/
     + 依托于browserlist，设置兼容浏览器的列表 https://github.com/browserslist/browserslist
     + 对于一些特殊的ES6语法，需要基于babel的插件处理！！
     + 只能实现语法的转换，对于ES6内置的API是转换不了的！！
   + ES6“内置API”的兼容处理：@babel/polyfill
     + 原理：对很多（不是所有）内置的API进行了重写
     + https://babeljs.io/docs/en/babel-polyfill

 ==============
 数组中常用的方法「Array」
   Array对象的静态私有属性方法 -> Array.xxx()
     + from「ES6/polyfill」把类数组转化为数组
     + isArray 检测一个值是否为数组
     + of「ES6」 创建一个新数组，并且把of中传递的值作为数组中的内容
     + ...

   Array原型上提供的，供其实例调用的公共属性和方法 -> 实例.xxx()
     + push/pop/shift/unshift/splice
     + slice
     + concat
     + join/toString
     + indexOf/lastIndexOf
     + includes「ES7/polyfill」
     + sort/reverse
     -------迭代(遍历)数组的方法
     + forEach
     + map 支持返回值
     + filter 筛选，依次迭代数组每一项，按照条件筛选出需要的内容；原始数组不变，把筛选出来的结果组成新数组（没有任何匹配则返回空数组）
     + find「ES6/polyfill」 查找，依次迭代数组每一项，找到符合条件的“这一项”（即便有多项符合条件，找到一项就不找了；一项都没匹配，则返回undefined）
     + findIndex「ES6/polyfill」 找到符合条件这一项的索引
     + some 迭代数组每一项，只要有一项符合条件则返回TRUE；所有项都不符合条件才会返回FALSE！
     + every 所有项都符合条件才返回TRUE；只要有一项不符合就返回FALSE！
     + reduce 迭代数组每一项，而且可以实现结果的累积
     + reduceRight 是从数组最后一项向第一项迭代，和reduce的迭代方向相反
     -------其他方法
     + copyWithin(z,x,y)「ES6/polyfill」 把从索引x开始找到索引y(不含y)的内容拷贝一份，替换索引z位置的内容
     + fill「ES6/polyfill」把数组中所有的内容按照固定值进行填充
     + flat「ES10」把多维数组进行扁平化处理
     + ...

  ======================================================================
  对象中常用的方法「Object」
    可枚举：能够被for/in循环迭代到、或者被Object.keys等访问到的属性
      + 一般情况下，浏览器内置的属性都是不可枚举的，自定义的属性都是可枚举的
      + 但是我们可以自己设定一个属性的“枚举型”

    Object原型上提供的，供其实例调用的方法 -> 实例.xxx()
      + hasOwnProperty 检测是否是私有属性
      + isPrototypeOf 检测“某个原型对象”是否出现在“某个实例”的原型链上
        “某个原型对象”.isPrototypeOf(“某个实例”)
      + propertyIsEnumerable 检测对象的某个属性是否是“可枚举”的
      + toString 检测数据类型的
      + valueOf 获取原始值的
      + ...
    
    Object对象上提供的静态私有属性方法 -> Object.xxx()
      + assign(obj1,obj2,obj3,...)「ES6」 浅合并多个对象，依次拿obj2/obj3浅替换obj1，最后返回obj1(被修改)
      + create(obj) 创建一个空对象，并且把obj作为其原型
      + getPrototypeOf(obj) 获取实例对象的原型链(原型对象)
      + setPrototypeOf(obj,prototype) 设置实例对象的原型指向「兼容IE11及以上」
      + is(value1,value2)「ES6/polyfill」比较两个值(基于“===”完成的)，特殊：Object.is(NaN,NaN)=>true
      -----获取键值对
      + keys(obj)  获取对象“可枚举、非Symbol类型的”私有的属性，返回包含属性名的数组
      + values(obj)「ES8/polyfill」 返回包含属性值的数组
      + entries(obj)「ES8/polyfill」 返回包含属性名和属性值的数组
      + getOwnPropertyNames(obj) 获取对象“非Symbol类型的”的私有属性，无关枚举性
      + getOwnPropertySymbols(obj)「ES6」 获取对象“Symbol类型的”的私有属性，无关枚举性
        let keys=Object.getOwnPropertyNames(obj);
        keys=keys.concat(Object.getOwnPropertySymbols(obj));
        //获取所有的私有属性「无关是否枚举，无关类型」
      -----设置属性规则
      + defineProperty/defineProperties 给当前对象的某个属性(或多个属性)设置规则和进行数据劫持
        规则：是否可枚举 enumerable、是否可修改 writable、是否可删除 configurable、初始值 value
        默认情况下：
          + 内置的属性方法(私有的/原型上的)一般都是不可枚举的,自己设定的属性方法一般都是可枚举的
          + 但是我们可以基于Object.defineProperty修改属性的规则
        数据劫持：GET/SET
      + getOwnPropertyDescriptor/getOwnPropertyDescriptors 查看对象某个属性(或全部属性)的规则
      -----对象特殊规则的处理
      + freeze(obj) 冻结一个对象「不能删除、修改、新增、劫持」
        + 把对象中每个属性的规则设置为“不可删除、不可修改”，枚举性不变(之前是啥还是啥)
        + 对象也无法在扩展新的属性了
        + 也无法基于defineProperty对属性做数据劫持了
      + isFrozen(obj) 检测对象是否被冻结

      + seal(obj) 密封一个对象「不能删除、新增、劫持，但是可以修改」
        + 把现有属性设置为不可删除，但是修改/枚举性和之前保持一致
        + 不能扩展新的属性
        + 也无法对属性进行劫持
      + isSealed(obj) 检测对象是否是密封的

      + preventExtensions(obj) 让对象变为不可扩展的「不能新增」
        + 原有的属性规则没有任何变化，也可以做劫持
        + 只是不能设置新的属性了
      + isExtensible(obj) 检测是否为可扩展的

   ==============
   ES6中新增了一个Reflect对象，聚合了关于“操作对象”的相关方法
      特点：不兼容IE的(而且polyfill也没有重写)；即便不使用Reflect中的方法，基于之前讲的方法，也可以实现对“对象”的相关操作！！
      + deleteProperty(target, propertyKey) 等同于delete，删除对象的某个属性
      + get(target, propertyKey) 获取对象某个成员的值，等同于target[propertyKey]
      + has(target, propertyKey) 等同于in操作符，校验当前对象中是否存在这个属性
      + set(target, propertyKey, value) 设置当前对象的某个成员的属性值，等同于target[propertyKey]=value
      + ownKeys(target) 获取当前对象所有的私有属性「包含：可枚举和不可枚举的、Symbol和非Symbol类型的」
      + ...
*/

/* 
let obj = {
  x: 10,
  y: 20,
  z: 30
};
// Object.freeze(obj); //冻结对象
// console.log(Object.isFrozen(obj)); //检测是否被冻结  true

// Object.seal(obj); //密封一个对象
// console.log(Object.isSealed(obj)); //检测是否是密封的 true

Object.preventExtensions(obj); //设置为不可扩展
console.log(Object.isExtensible(obj)); //检测是否可扩展 false
console.log(Object.getOwnPropertyDescriptors(obj)); 
*/

/* defineProperty的第二个作用：给对象的某个属性(或多个属性)进行数据劫持(GET/SET) */
/* let obj = {
  x: 10,
  y: 20,
  z: 30
};
let proxy = { ...obj };
Object.defineProperty(obj, 'x', {
  get() {
    return proxy.x;
  },
  set(val) {
    proxy.x = val;
  }
}); */

/* Object.defineProperty(obj, 'x', {
  // 规则中的 writable/value 和数据劫持是相冲突的
  configurable: false,
  enumerable: false,
  // 获取obj.x属性值的时候，触发GET函数；函数的返回值，就是获取的属性值！
  get() {
    console.log('GET');
    return '珠峰';
  },
  // 设置obj.x属性值的时候触发SET函数；val是我们想要设置的属性值；
  set(val) {
    console.log('SET', val);
  }
}); */

/*
 对象“属性的规则”
   + 是否可删除 configurable
   + 是否可枚举 enumerable
   + 是否可修改 writable
   + 初始值 value
 正常情况下，基于字面量(或者成员访问)的方式，为对象设置的属性，其规则是：
   可删除、可修改、可枚举、初始值就是赋的值
 但是我们可以基于 Object.defineProperty/Object.defineProperties 给现有属性修改规则，或者是新增一个“新的属性”！
 */
/*
let obj = {
  x: 10,
  y: 20,
  z: 30
};
// 修改现有属性的规则
Object.defineProperty(obj, 'x', {
  configurable: false,
  writable: false,
  enumerable: false,
  value: 100
});
// 新增"新的属性"，而这种新增方式，默认规则是：不可删除、不可修改、不可枚举、初始值undefined；但是可以在新增的同时，修改规则！！
// Object.defineProperty(obj, 'age', {});
Object.defineProperty(obj, 'age', {
  configurable: true,
  writable: true,
  enumerable: false,
  value: 25
});
console.log(Object.getOwnPropertyDescriptor(obj, 'age'));
*/


/* let arr = [10, 20];
arr['name'] = '数组';
arr[Symbol('AA')] = 100;
console.log(arr); */

/*
let obj1 = {
  x: 10,
  y: '珠峰',
  list: { n: 100 }
};
let obj2 = {
  x: 100,
  list: { m: 200 },
  z: 30
};
let obj = Object.assign(obj1, obj2);
// obj2去覆盖obj1，修改的是obj1这个堆内存(obj2不变)，返回的是obj1堆内存地址
//  + 两者都有的属性，以obj2为主   x->100
//  + 浅合并   list->{m:200}
//  + 1有2没有的，还是以1为主  y->'珠峰'
//  + 2有1没有的，则给1新增相同的属性及属性值  z->30
console.log(obj === obj1); //true
console.log(obj1, obj2); //第一个被改  第二个没有被改
// 在不修改1/2的情况下，还可以进行合并
let newObj = Object.assign({}, obj1, obj2);
*/

//==========================================
/* 
// flat：数组扁平化  ES6「如果需要兼容处理，则需要基于其它方案？（扩展：总结数组扁平化的方案）」
// let arr = [10, [20, [30, [40, [50]]]], 60];
// arr = arr.flat(); //不写默认是1「只降了一维」
// console.log(arr);
// let arr = [10, [20, [30, [40, [50]]]], 60];
// arr = arr.flat(Infinity); //不论数组是几维的，直接降为1维数组
// console.log(arr);  //[10, 20, 30, 40, 50, 60]

// fill：对数据中所有项进行填充，都替换为填充的内容  ES6/polyfill
// let arr = [10, 20, 30, 40, 50];
// arr.fill(null);
// console.log(arr);
// new Array(10).fill(null) 把稀疏数组变为密集数组，这样就可以使用迭代方法了

// // copyWithin：保持数组长度是不变的  ES6/polyfill
// let arr = [10, 20, 30, 40, 50];
// arr.copyWithin(1, 3);  //复制的内容 40,50  替换索引1的位置(从索引1开始，把和复制内容相同长度的内容都替换掉)
// console.log(arr); //[10,40,50,40,50]

// // at(index)：按照索引，获取指定项的内容「支持负数索引」 ES6
// let arr = [10, 20, 30, 40, 50];
// console.log(arr[arr.length - 1]); //50
// console.log(arr.at(-1)); //50
// console.log(arr.slice(-1)); //[50] 
*/

/* 
//======数组中9个“迭代”方法：循环数组每一项的同时，做一些特殊的处理
// 数组有多少项，就需要迭代多少次，不支持迭代中结束循环「但是可以自己写判断，控制啥都不做」
// 也无法间隔着循环
// 如果需要很灵活/复杂的迭代模式，则建议使用“命令式编程”！
// let arr = [10, 20, 30, 40, 50];

// let total = arr.reduce((res, item, index) => {
//   // 第一轮：res=0「reduce中的第二个参数就是为res赋值初始值」 item=10  index=0 从第一项开始迭代
//   return res + item;
// }, 0);

// let total = arr.reduce((res, item, index) => {
//   // 第一轮：res=10 item=20 index=1 「res初始值是第一项，从数组第二项开始迭代」 返回：30
//   // 第二轮：res=30「上一轮处理的结果，会传递给下一轮的res」 item=30 index=2  返回：60
//   // 第三轮：res=60 ....
//   // 整个循环都结束后，把最终的处理结果赋值给total
//   return res + item;
// });
// console.log(total);

// console.log(arr.some(item => item > 20)); //true
// console.log(arr.every(item => item > 20)); //false

// let result = arr.find(item => item > 20);
// console.log(result); //30

// arr = arr.filter(item => {
//   return item !== 20 && item !== 40;
// });
// console.log(arr);

// let result = arr.filter((item) => item > 300);
// console.log(result); //[]

// let result = arr.filter((item, index) => {
//   // 回调方法中返回true，则这一项被筛选到，返回false，则这一项不要；原始数组不变，返回新数组！
//   return item > 30;
// });
// console.log(result); //[40,50]

// arr.forEach((value, index) => {
//   console.log(value, index);
// });

// let arr2 = arr.map((value, index) => {
//   return value * index;
// });
// console.log(arr, arr2)
*/


/* 
//=====Array.of
console.log(new Array()); //创建空数组
console.log(Array.of());

console.log(new Array(10)); //创建长度为10的“稀疏”数组
console.log(Array.of(10)); //创建一个数组，数组第一项是10

console.log(new Array('10')); //['10']
console.log(Array.of('10')); //['10']

console.log(new Array(10, 20)); //[10, 20]
console.log(Array.of(10, 20)); //[10, 20] 
*/


/* 
//=====Array.from
let obj = {
  0: 10,
  1: 20,
  2: 30,
  length: 3
};
// let arr = Array.from(obj);
// console.log(arr);

let x = {};
let arr = Array.from(obj, function (item, index) {
  // this->x && obj集合中有多少项，则回调函数触发执行多少次 
  // item/index：每一次迭代获取的当前项和索引
  // 返回值就是转换后，数组中相同索引这一项的值
  return item * 10;
}, x);
console.log(arr); 
*/